<template>
  <div class="chat-input-area">
    <div
      :class="['edit-chat-container', isEmpty ? '' : 'not-empty']"
      contenteditable="true"
      @paste.prevent="onPaste"
      @keydown="onKeydown"
      @compositionstart="compositionFlag = true"
      @compositionend="onCompositionEnd"
      @input="onEditorInput"
      @mousedown="onMousedown"
      ref="content"
      @blur="onBlur"
    ></div>
    <!-- <chat-at-box
      @select="onAtSelect"
      :search-text="atSearchText"
      ref="atBox"
      :ownerId="ownerId"
      :members="groupMembers"
    ></chat-at-box> -->
  </div>
</template>

<script setup>
import { ref, reactive, nextTick } from "vue";

const props = defineProps({
  ownerId: {
    type: Number,
  },
  groupMembers: {
    type: Array,
  },
});

const emit = defineEmits(["submit"]);

const content = ref(null);
const atBox = ref(null);

const state = reactive({
  imageList: [],
  fileList: [],
  currentId: 0,
  atSearchText: null,
  compositionFlag: false,
  atIng: false,
  isEmpty: true,
  changeStored: true,
  blurRange: null,
});

const generateId = () => {
  return state.currentId++;
};

const selectElement = (element, endOffset) => {
  let selection = window.getSelection();
  nextTick(() => {
    let t1 = document.createRange();
    t1.setStart(element, 0);
    t1.setEnd(element, endOffset || 0);
    if (element.firstChild) {
      t1.selectNodeContents(element.firstChild);
    }
    t1.collapse();
    selection.removeAllRanges();
    selection.addRange(t1);
    if (element.focus) {
      element.focus();
    }
  });
};

const onPaste = (e) => {
  state.isEmpty = false;
  let txt = e.clipboardData.getData("Text");
  let range = window.getSelection().getRangeAt(0);
  if (
    range.startContainer !== range.endContainer ||
    range.startOffset !== range.endOffset
  ) {
    range.deleteContents();
  }
  if (txt && typeof txt == "string") {
    let textNode = document.createTextNode(txt);
    range.insertNode(textNode);
    range.collapse();
    return;
  }
  let items = (e.clipboardData || window.clipboardData).items;
  if (items.length) {
    for (let i = 0; i < items.length; i++) {
      if (items[i].type.indexOf("image") !== -1) {
        let file = items[i].getAsFile();
        let imagePush = {
          fileId: generateId(),
          file: file,
          url: URL.createObjectURL(file),
        };
        state.imageList[imagePush.fileId] = imagePush;
        let line = newLine();
        let imageElement = document.createElement("img");
        imageElement.className = "chat-image no-text";
        imageElement.src = imagePush.url;
        imageElement.dataset.imgId = imagePush.fileId;
        line.appendChild(imageElement);
        let after = document.createTextNode("\u00A0");
        line.appendChild(after);
        selectElement(after, 1);
      } else {
        let asFile = items[i].getAsFile();
        if (!asFile) {
          continue;
        }
        let filePush = { fileId: generateId(), file: asFile };
        state.fileList[filePush.fileId] = filePush;
        let line = newLine();
        let fileElement = createFile(filePush);
        line.appendChild(fileElement);
        let after = document.createTextNode("\u00A0");
        line.appendChild(after);
        selectElement(after, 1);
      }
    }
  }
  range.collapse();
};

const onCompositionEnd = (e) => {
  state.compositionFlag = false;
  onEditorInput(e);
};

const onKeydown = (e) => {
  if (e.keyCode === 13) {
    e.preventDefault();
    e.stopPropagation();
    if (state.atIng) {
      atBox.value.select();
      return;
    }
    if (e.ctrlKey) {
      let line = newLine();
      let after = document.createTextNode("\u00A0");
      line.appendChild(after);
      selectElement(line.childNodes[0], 0);
    } else {
      if (state.compositionFlag) {
        return;
      }
      submit();
    }
    return;
  }
  if (e.keyCode === 8) {
    setTimeout(() => {
      let s = content.value.innerHTML.trim();
      if (s === "" || s === "<br>" || s === "<div>&nbsp;</div>") {
        empty();
        state.isEmpty = true;
        selectElement(content.value);
      } else {
        state.isEmpty = false;
      }
    });
  }
  if (state.atIng) {
    if (e.keyCode === 38) {
      e.preventDefault();
      e.stopPropagation();
      atBox.value.moveUp();
    }
    if (e.keyCode === 40) {
      e.preventDefault();
      e.stopPropagation();
      atBox.value.moveDown();
    }
  }
};

const onAtSelect = (member) => {
  state.atIng = false;
  let blurRange = state.blurRange;
  let endContainer = blurRange.endContainer;
  let startOffset = endContainer.data.indexOf("@" + state.atSearchText);
  let endOffset = startOffset + state.atSearchText.length + 1;
  blurRange.setStart(blurRange.endContainer, startOffset);
  blurRange.setEnd(blurRange.endContainer, endOffset);
  blurRange.deleteContents();
  blurRange.collapse();
  focus();

  let element = document.createElement("SPAN");
  element.className = "chat-at-user";
  element.dataset.id = member.userId;
  element.contentEditable = "false";
  element.innerText = `@${member.showNickName}`;
  blurRange.insertNode(element);
  blurRange.collapse();

  let textNode = document.createTextNode("\u00A0");
  blurRange.insertNode(textNode);

  blurRange.collapse();
  state.atSearchText = "";
  selectElement(textNode, 1);
};

const onEditorInput = (e) => {
  state.isEmpty = false;
  state.changeStored = false;
  if (props.groupMembers && !state.compositionFlag) {
    let selection = window.getSelection();
    let range = selection.getRangeAt(0);
    let endContainer = range.endContainer;
    let endOffset = range.endOffset;
    let textContent = endContainer.textContent;
    let startIndex = -1;
    for (let i = endOffset; i >= 0; i--) {
      if (textContent[i] === "@") {
        startIndex = i;
        break;
      }
    }
    if (startIndex === -1) {
      atBox.value.close();
      return;
    }

    let endIndex = endOffset;
    for (let i = endOffset; i < textContent.length; i++) {
      if (textContent[i] === " ") {
        endIndex = i;
        break;
      }
    }
    state.atSearchText = textContent.substring(startIndex + 1, endIndex).trim();
    if (state.atSearchText == "") {
      showAtBox(e);
    }
  }
};

const onBlur = (e) => {
  if (!state.atIng) {
    updateRange();
  }
};

const onMousedown = () => {
  if (state.atIng) {
    atBox.value.close();
    state.atIng = false;
  }
};

const insertEmoji = (emojiText) => {
  let emojiElement = document.createElement("img");
  emojiElement.className = "emoji-normal no-text";
  emojiElement.dataset.emojiCode = emojiText;
  emojiElement.src = inject("emo").textToUrl(emojiText);

  let blurRange = state.blurRange;
  if (!blurRange) {
    focus();
    updateRange();
    blurRange = state.blurRange;
  }
  if (
    blurRange.startContainer !== blurRange.endContainer ||
    blurRange.startOffset !== blurRange.endOffset
  ) {
    blurRange.deleteContents();
  }
  blurRange.insertNode(emojiElement);
  blurRange.collapse();

  let textNode = document.createTextNode("\u00A0");
  blurRange.insertNode(textNode);
  blurRange.collapse();

  selectElement(textNode);
  updateRange();
  state.isEmpty = false;
};

const createFile = (filePush) => {
  let file = filePush.file;
  let fileId = filePush.fileId;
  let container = document.createElement("div");
  container.className = "chat-file-container no-text";
  container.contentEditable = "false";
  container.dataset.fileId = fileId;

  let left = document.createElement("div");
  left.className = "file-position-left";
  container.appendChild(left);

  let icon = document.createElement("div");
  icon.className = "el-icon-document";
  left.appendChild(icon);

  let right = document.createElement("div");
  right.className = "file-position-right";
  container.appendChild(right);

  let fileName = document.createElement("div");
  fileName.className = "file-name";
  fileName.innerText = file.name;

  let fileSize = document.createElement("div");
  fileSize.className = "file-size";
  fileSize.innerText = sizeConvert(file.size);

  right.appendChild(fileName);
  right.appendChild(fileSize);

  return container;
};

const sizeConvert = (len) => {
  if (len < 1024) {
    return len + "B";
  } else if (len < 1024 * 1024) {
    return (len / 1024).toFixed(2) + "KB";
  } else if (len < 1024 * 1024 * 1024) {
    return (len / 1024 / 1024).toFixed(2) + "MB";
  } else {
    return (len / 1024 / 1024 / 1024).toFixed(2) + "GB";
  }
};

const updateRange = () => {
  let selection = window.getSelection();
  state.blurRange = selection.getRangeAt(0);
};

const newLine = () => {
  let selection = window.getSelection();
  let range = selection.getRangeAt(0);
  let divElement = document.createElement("div");
  let endContainer = range.endContainer;
  let parentElement = endContainer.parentElement;
  if (parentElement.parentElement === content.value) {
    divElement.innerHTML = endContainer.textContent
      .substring(range.endOffset)
      .trim();
    endContainer.textContent = endContainer.textContent.substring(
      0,
      range.endOffset
    );
    parentElement.insertAdjacentElement("afterend", divElement);
  } else {
    divElement.innerHTML = "";
    content.value.append(divElement);
  }
  return divElement;
};

const clear = () => {
  empty();
  state.imageList = [];
  state.fileList = [];
  atBox.value.close();
};

const empty = () => {
  content.value.innerHTML = "";
  let line = newLine();
  let after = document.createTextNode("\u00A0");
  line.appendChild(after);
  nextTick(() => selectElement(after));
};

const showAtBox = (e) => {
  state.atIng = true;
  let selection = window.getSelection();
  let range = selection.getRangeAt(0);
  let pos = range.getBoundingClientRect();
  atBox.value.open({
    x: pos.x,
    y: pos.y,
  });
  updateRange();
};

const html2Escape = (strHtml) => {
  return strHtml.replace(/[<>&"]/g, function (c) {
    return {
      "<": "&lt;",
      ">": "&gt;",
      "&": "&amp;",
      '"': "&quot;",
    }[c];
  });
};

const submit = () => {
  let nodes = content.value.childNodes;
  let fullList = [];
  let tempText = "";
  let atUserIds = [];
  let each = (nodes) => {
    for (let i = 0; i < nodes.length; i++) {
      let node = nodes[i];
      if (!node) {
        continue;
      }
      if (node.nodeType === 3) {
        tempText += html2Escape(node.textContent);
        continue;
      }
      let nodeName = node.nodeName.toLowerCase();
      if (nodeName === "script") {
        continue;
      }
      let text = tempText.trim();
      if (nodeName === "img") {
        let imgId = node.dataset.imgId;
        if (imgId) {
          if (text) {
            fullList.push({
              type: "text",
              content: text,
              atUserIds: atUserIds,
            });
            tempText = "";
            atUserIds = [];
          }
          fullList.push({
            type: "image",
            content: state.imageList[imgId],
          });
        } else {
          let emojiCode = node.dataset.emojiCode;
          tempText += emojiCode;
        }
      } else if (nodeName === "div") {
        let fileId = node.dataset.fileId;
        if (fileId) {
          if (text) {
            fullList.push({
              type: "text",
              content: text,
              atUserIds: atUserIds,
            });
            tempText = "";
            atUserIds = [];
          }
          fullList.push({
            type: "file",
            content: state.fileList[fileId],
          });
        } else {
          tempText += "\n";
          each(node.childNodes);
        }
      } else if (nodeName === "span") {
        if (node.dataset.id) {
          tempText += node.innerHTML;
          atUserIds.push(node.dataset.id);
        } else if (node.outerHtml) {
          tempText += node.outerHtml;
        }
      }
    }
  };
  each(nodes);
  let text = tempText.trim();
  if (text !== "") {
    fullList.push({
      type: "text",
      content: text,
      atUserIds: atUserIds,
    });
  }
  emit("submit", fullList);
};

const focus = () => {
  content.value.focus();
};
</script>

<style lang="scss">
.chat-input-area {
  width: 100%;
  height: 100%;
  position: relative;

  .edit-chat-container {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    outline: none;
    padding: 5px;
    line-height: 26px;
    font-size: var(--im-font-size);
    text-align: left;
    overflow-y: auto;

    > div:before {
      content: "\00a0";
      font-size: 14px;
      position: absolute;
      top: 0;
      left: 0;
    }

    .chat-image {
      display: block;
      max-width: 200px;
      max-height: 100px;
      border: 1px solid #e6e6e6;
      cursor: pointer;
    }

    .chat-file-container {
      max-width: 65%;
      padding: 10px;
      border: 2px solid #587ff0;
      display: flex;
      background: #eeec;
      border-radius: 10px;

      .file-position-left {
        display: flex;
        width: 80px;
        justify-content: center;
        align-items: center;

        .el-icon-document {
          font-size: 40px;
          text-align: center;
          color: #d42e07;
        }
      }

      .file-position-right {
        flex: 1;

        .file-name {
          font-size: 16px;
          font-weight: 600;
          color: #66b1ff;
        }

        .file-size {
          font-size: 14px;
          font-weight: 600;
        }
      }
    }

    .chat-at-user {
      color: #00f;
      border-radius: 3px;
    }
  }

  .edit-chat-container > div:nth-of-type(1):after {
    content: "请输入消息（按Ctrl+Enter键换行）";
    color: gray;
  }

  .edit-chat-container.not-empty > div:nth-of-type(1):after {
    content: none;
  }
}
</style>
